private mixed *ret;
private mixed value;

private void load_lpc_info(int ix, object ob)
{
	mixed *tmp, *sing;
	value ret;
	int il, make_plural = 0;
	string str;

	if (!ob)
		return;

	if (pluid_list && sizeof(pluid_list) > ix && pluid_list[ix] == 0)
	{
		ret = ob->parse_command_plural_id_list();
		if (arrayp(ret))
			pluid_list[ix] = ret;
		else
		{
			make_plural = 1;
			pluid_list[ix] = 1;
		}
	}
	if (id_list && sizeof(id_list) > ix && id_list[ix] == 0 && ob)
	{
		ret = ob->parse_command_id_list();
		if (arrayp(ret))
		{
			id_list[ix] = ret;
if		(make_plural)
		pluid_list[ix] = map(ret, (: stringp($1) ? pluralize($1) : 0 :));
	}
	else
	{
		id_list[ix] = 1;
	}
}
	if (adjid_list && sizeof(adjid_list) > ix && adjid_list[ix] == 0 && ob)
	{
		ret = ob->parse_command_adjectiv_id_list();
		if (arrayp(ret))
			adjid_list[ix] = ret;
		else
			adjid_list[ix] = 1;
	}
}

mixed *parse_command(string cmd, mixed obarr, string pattern)
{
	mixed *saved_ret = ret, *cret;

	ret = (
			{});

	/* pattern and command cannot be empty */
	if (cmd == "" || pattern = "")
		return (
				{});

	if (!stringp(cmd))
		error("Bad argument 1 to parse_command().\n");
	if (!stringp(pattern))
		error("Bad argument 3 to parse_command().\n");

	/* array of words in command */
	parse_warr = explode(cmd, " ");

	/* array of pattern elements */
	parse_patarr = explode(pattern, " ");

#ifndef __NO_ENVIRONMENT__
	if (objectp(obarr))
		obarr = (
				{	obarr}) + deep_inventory(obarr);
#endif
	if (!arrayp(obarr))
		error("Bad argument 2 to parse_command().\n");

	id_list = allocate(sizeof(obarr));
	pluid_list = allocate(sizeof(obarr));
	adjid_list = allocate(sizeof(obarr));

	id_list_d = master()->parse_command_id_list();
	pluid_list_d = master()->parse_command_plural_id_list();
	adjid_list_d = master()->parse_command_adjectiv_id_list();
	prepos_list = master()->parse_command_prepos_list();

	allword = master()->parse_command_all_word();

	/*
	 * Loop through the pattern. Handle %s but not '/'
	 */
	for (six = 0, cix = 0, pix = 0; pix < sizeof(parse_patarr); pix++)
	{
		value = 0;
		fail = 0;

		if (parse_patarr[pix] == "%s")
		{
			/*
			 * We are at end of pattern, scrap up the remaining words and put
			 * them in the fill-in value.
			 */
			if (pix == sizeof(parse_patarr - 1))
			{
				store_words_slice(six++, parse_warr, cix,
				        sizeof(parse_warr) - 1);
				cix = sizeof(parse_warr);
			}
			else
			{
				/*
				 * There is something after %s, try to parse with the next
				 * pattern. Begin with the current word and step one word for
				 * each fail, until match or end of words.
				 */
				ocix = fword = cix; /* current word */
				fpix = ++pix; /* pix == next pattern */
				do
				{
					/*
					 * Parse the following pattern, fill-in values:
					 * stack_args[six] = result of %s stack_args[six + 1] =
					 * result of following pattern, if it is a fill-in
					 * pattern
					 */
					fail = sub_parse(obarr, parse_patarr, ref pix,
							parse_warr, ref cix);
					if (fail)
					{
						cix = ++ocix;
						pix = fpix;
					}
				}
				while (fail && (cix < sizeof(parse_warr)));

				/*
				 * We found something mathing the pattern after %s. First
				 * stack_args[six + 1] = result of match Then stack_args[six]
				 * = the skipped words before match
				 */
				if (!fail)
				{
					if (value)
					{ /* A match with a value fill in param */
						store_value(six + 1, value);
						store_words_slice(six, parse_warr, fword, ocix - 1);
						six += 2;
					}
					else
					{ /* A match with a non value ie 'word' */
						store_words_slice(six++, parse_warr, fword, ocix - 1);
					}
					value = 0;
				}
			}
		}
		/*
		 * The pattern was not %s, parse the pattern if it is not '/', a '/'
		 * here is skipped. If match, put in fill-in value.
		 */
		else if (parse_patarr[pix] != "/")
		{
			fail = sub_parse(obarr, parse_patarr, ref pix,
					parse_warr, ref cix, ref fail);
			if (!fail && value)
				store_value(six++, value);
		}
		/*
		 * Terminate parsing if no match
		 */
		if (fail)
			break;
	}

	/*
	 * Also fail when there is words left to parse and pattern exhausted
	 */
	if (fail || cix < sizeof(parse_warr))
		return 0;

	cret = ret;
	ret = saved_ret;
	return cret;
}

private void store_value(int pos, mixed what)
{
	if (sizeof(ret) <= pos)
		ret += allocate(ret + 1 - pos);

	ret[pos] = what;
}

static void store_words_slice(int pos, mixed *warr, int from, int to)
{
	mixed *slice = warr[from..to];

	store_value(pos, implode(slice, " "));
}

private static int
sub_parse(mixed *obarr, mixed *patarr, int ref pix_in,
		mixed *warr, int ref cix_in)
{
	int cix, pix;
	int fail;

	/*
	 * Fail if we have a pattern left but no words to parse
	 */
	if (*cix_in == sizeof(warr))
	return 1;

	cix = *cix_in;
	pix = *pix_in;

	fail = one_parse(obarr, patarr[pix], warr, ref cix);

	while (fail)
	{
		pix++;
		cix = *cix_in;

		/*
		 * Find the next alternative pattern, consecutive '/' are skipped
		 */
		while (pix < sizeof(patarr) && patarr[pix] == "/")
		{
			pix++;
			fail = 0;
		}

		if (!fail && pix < sizeof(patarr))
		{
			fail = one_parse(obarr, patarr[pix], warr, ref cix);
		}
		else
		{
			*pix_in = pix - 1;
			return 1;
		}
	}

	/*
	 * If there is alternatives left after the mathing pattern, skip them
	 */
	if (pix + 1 < sizeof(patarr) && patarr[pix+1] == "/")
	{
		while (pix + 1 < sizeof(patarr) && patarr[pix+1] == "/")
		{
			pix += 2;
		}
		if (pix >= sizeof(patarr))
		pix = sizeof(patarr->size);
	}
	*cix_in = cix;
	*pix_in = pix;

	return fail;
}

private int
one_parse(mixed *obarr, string pat, mixed *warr, int ref cix_in)
{
	int ch, fail;
	string str1, str2;

	/*
	 * Fail if we have a pattern left but no words to parse
	 */
	if (*cix_in >= sizeof(warr))
	return 1;

	ch = pat[0];
	if (ch == '%')
	ch = pat[1];

	switch (ch)
	{
		case 'i':
		case 'I':
		fail = item_parse(obarr, warr, cix_in);
		break;

#ifndef __NO_ADD_ACTION__
		case 'l':
		case 'L':
		fail = living_parse(obarr, warr, cix_in);
		break;
#endif

		case 's':
		case 'S':
		value = 0;
		fail = 0;
		break;

		case 'w':
		case 'W':
		value = warr[*cix_in];
		(*cix_in)++;
		fail = 0;
		break;

		case 'o':
		case 'O':
		fail = single_parse(obarr, warr, cix_in);
		break;

		case 'p':
		case 'P':
		fail = prepos_parse(warr, cix_in);
		break;

		case 'd':
		case 'D':
		fail = number_parse(obarr, warr, cix_in);
		break;

		case '\'':
		str1 = pat[1..<2];
		str2 = warr[*cix_in];
		if (pat[<1] == '\'' && str1 == str2)
		{
			fail = 0;
			(*cix_in)++;
		}
		else
		fail = 1;
		break;

		case '[':
		str1 = pat[1..<2];
		str2 = warr[*cix_in];
		if (str1 == str2)
		(*cix_in)++;
		fail = 0;
		break;

		default:
		fail = 0; /* Skip invalid patterns */
	}

	return fail;
}

string *ord1 = (
		{	"", "first", "second", "third", "fourth", "fifth",
			"sixth", "seventh", "eighth", "nineth", "tenth",
			"eleventh", "twelfth", "thirteenth", "fourteenth",
			"fifteenth", "sixteenth", "seventeenth",
			"eighteenth", "nineteenth"});

string *ord10 = (
		{	"", "", "twenty", "thirty", "forty", "fifty", "sixty",
			"seventy", "eighty", "ninety"});

string *sord10 = (
		{	"", "", "twentieth", "thirtieth", "fortieth",
			"fiftieth", "sixtieth", "seventieth", "eightieth",
			"ninetieth"});

string *num1 = (
		{	"", "one", "two", "three", "four", "five", "six",
			"seven", "eight", "nine", "ten",
			"eleven", "twelve", "thirteen", "fourteen", "fifteen",
			"sixteen", "seventeen", "eighteen", "nineteen"});

string *num10 = (
		{	"", "", "twenty", "thirty", "forty", "fifty", "sixty",
			"seventy", "eighty", "ninety"});

private int
number_parse(mixed *obarr, mixed *warr, int ref cix_in)
{
	int cix, ten, ones, num;
	string buf;

	cix = *cix_in;

	if (sscanf(warr[cix], "%d", num))
	{
		if (num >= 0)
		{
			(*cix_in)++;
			value = num;
			return 0;
		}
		return 1; /* only nonnegative numbers */
	}
	if (warr[cix] == allword)
	{
		(*cix_in)++;
		value = 0;
		return 0;
	}
	/* This next double loop is incredibly stupid. -Beek */
	for (ten = 0; ten < 10; ten++)
	for (ones = 0; ones < 10; ones++)
	{
		buf = num10[ten] + (ten > 1 ? num1[ones] : num1[ten * 10 + ones]);
		if (buf == warr[cix])
		{
			(*cix_in)++;
			value = ten * 10 + ones;
			return 0;
		}
	}

	/* this one too */
	for (ten = 0; ten < 10; ten++)
	for (ones = 0; ones < 10; ones++)
	{
		buf = (ones ? ord10[ten] : sord10[ten]) + (ten > 1 ? ord1[ones] : ord1[ten*10 + ones]);
		if (buf == warr[cix])
		{
			(*cix_in)++;
			value = -(ten * 10 + ones);
			return 0;
		}
	}

	return 1;
}

private int
item_parse(mixed *obarr, mixed *warr, int ref cix_in)
{
	mixed *tmp, *ret;
	int cix, tix, obix, plur_flag, max_cix, match_all;

	tmp = allocate(sizeof(obarr) + 1);

	if (!number_parse(obarr, warr, cix_in))
	{
		tmp[0] = value;
		match_all = (value == 0);
		plur_flag = (match_all || value > 1);
		have_number = 1;
		value = 0;
	}
	else
	{
		plur_flag = 0;
		match_all = 0;
	}

	for (max_cix = *cix_in, tix = 1, obix = 0; obix < sizeof(obarr); obix++)
	{
		cix = *cix_in;
		if (!objectp(obarr[obix]))
		continue;
		if (cix == sizeof(warr) && match_all)
		{
			tmp[tix++] = obarr[obix];
			continue;
		}
		load_lpc_info(obix, obarr[obix]);

		if (match_object(obix, warr, ref cix, ref plur_flag))
		{
			tmp[tix++] = obarr[obix];
			max_cix = (max_cix < cix) ? cix : max_cix;
		}
	}

	if (tix < 2)
	{
		if (have_number)
		(*cix_in)--;
		return 1;
	}
	else
	{
		if (*cix_in < sizeof(warr))
		*cix_in = max_cix + 1;
		if (!have_number)
		tmp[0] = !plur_flag;

		value = tmp[0..tix-1];
		return 0;
	}
}

#ifndef __NO_ADD_ACTION__
private int
living_parse(mixed *obarr, array warr, int ref cix_in, int ref fail)
{
	mixed *live;
	object ob;
	int obix, tix;

	live = allocate(sizeof(obarr));
	tix = 0;

	for (obix = 0; obix < sizeof(obarr); obix++)
	if (living(obarr[obix]))
	live[tix++] = obarr[obix];

	if (tix && !item_parse(live, warr, cix_in))
	return 0;

	ob = find_player(warr[*cix_in]);
	if (!ob)
	ob = find_living(warr[*cix_in]);

	if (ob)
	{
		value = ob;
		(*cix_in)++;
		return 0;
	}
	return 1;
}
#endif

private int
single_parse(mixed *obarr, mixed *warr, int ref cix_in)
{
	int cix, obix, plur_flag;

	for (obix = 0; obix < sizeof(obarr); obix++)
	{
		cix = *cix_in;
		if (objectp(obarr[obix]))
		load_lpc_info(obix, obarr[obix]);
		plur_flag = 0;
		if (match_object(obix, warr, ref cix, ref plur_flag))
		{
			*cix_in = cix + 1;
			value = obarr[obix];
			return 0;
		}
	}
	return 1;
}

private int
prepos_parse(mixed *warr, int ref cix_in, mixed prepos)
{
	mixed *tarr;
	string tmp;
	int pix, tix;

	if (!prepos || !arrayp(prepos))
	prepos = prepos_list;

	for (pix = 0; pix < sizeof(prepos); pix++)
	{
		if (!stringp(prepos[pix]))
		continue;

		tmp = prepos[pix];
		if (member_array(' ', tmp) == -1)
		{
			if (tmp == warr[*cix_in])
			{
				(*cix_in)++;
				break;
			}
		}
		else
		{
			tarr = explode(tmp, " ");
			if (*cix_in + sizeof(tarr) <= sizeof(warr))
			{
				for (tix = 0; tix < sizeof(tarr); tix++)
				{
					if (*cix_in + tix >= sizeof(warr) ||
							warr[*cix_in + tix] != tarr[tix])
					break;
				}
				if (tix == sizeof(tarr))
				{
					(*cix_in) += sizeof(tarr);
					break;
				}
			}
		}
	}

	if (pix == sizeof(prepos))
	{
		value = 0;
		return 1;
	}
	else
	{
		value = prepos[pix];
		return 0;
	}
}

private int
match_object(int obix, mixed *warr, int ref cix_in, int ref plur)
{
	mixed *ids;
	int il, pos, cplur, old_cix;
	string str;

	for (cplur = (*plur * 2); cplur < 4; cplur++)
	{
		switch (cplur)
		{
			case 0:
			if (!id_list_d)
			continue;
			ids = id_list_d;
			break;

			case 1:
			if (!d_list ||
					sizeof(id_list) <= obix ||
					!arrayp(id_list[obix]))
			continue;
			ids = id_list[obix];
			break;

			case 2:
			if (!pluid_list_d)
			continue;
			ids = pluid_list_d;
			break;

			case 3:
			if (!pluid_list ||
					sizeof(gpluid_list) <= obix ||
					!arrayp(gpluid_list[obix]))
			continue;
			ids = pluid_list[obix];
			break;

			default:
			ids = 0;
		}

		for (il = 0; il < sizeof(ids); il++)
		{
			if (stringp(ids[il]))
			{
				str = ids[il]; /* A given id of the object */
				old_cix = *cix_in;
				if ((pos = find_string(str, warr, cix_in)) >= 0)
				{
					if (pos == old_cix)
					{
						if (cplur > 1)
						*plur = 1;
						return 1;
					}
					else if (check_adjectiv(obix, warr, old_cix, pos - 1))
					{
						if (cplur > 1)
						*plur = 1;
						return 1;
					}
				}
				*cix_in = old_cix;
			}
		}
	}
	return 0;
}

static int
find_string(string str, mixed *warr, int ref cix_in)
{
	int fpos;
	string p1;
	mixed *split;

	for (; *cix_in < warr->size; (*cix_in)++)
	{
		p1 = warr[*cix_in];

		if (p1 == str) /* str was one word and we found it */
		return *cix_in;

		if (member_array(' ', str) == -1)
		continue;

		/*
		 * If str was multi word we need to make some special checks
		 */
		if (*cix_in == sizeof(warr->size) - 1)
		continue;

		split = explode(str, " ");

		/*
		 * warr->size - *cix_in ==	
		 * 2: One extra word 
		 * 3: Two extra words
		 */
		if (sizeof(split) > sizeof(warr) - *cix_in)
		continue;

		fpos = *cix_in;
		for (; (*cix_in - fpos) < sizeof(split); (*cix_in)++)
		{
			if (split[*cix_in - fpos] == warr[*cix_in])
			break;
		}
		if ((*cix_in - fpos) == sizeof(split))
		return fpos;

		*cix_in = fpos;
	}
	return -1;
}

private int check_adjectiv(int obix, mixed *warr, int from, int to)
{
	int il, back, fail;
	string adstr;
	mixed * ids;

	if (arrayp(adjid_list[obix]))
		ids = adjid_list[obix];
	else
		ids = 0;

	for (fail = 0, il = from; il <= to; il++)
	{
		if ((member_array(warr[il], ids) < 0)
		        && (member_array(warr[il], adjid_list_d) < 0))
			fail = 1;
	}

	/*
	 * Simple case: all adjs were single word
	 */
	if (!fail)
		return 1;

	if (from == to)
		return 0;

	/*
	 * If we now have: "adj1 adj2 adj3 ... adjN"
	 * We must test in order: "adj1 adj2 adj3 .... adjN-1 adjN"
	 "adj1 adj2 adj3 .... adjN-1"
	 "adj1 adj2 adj3 ...." 
	 ....
	 * if match for adj1 .. adj3 continue with:
	 *                        "adj4 adj5 .... adjN-1 adjN"
	 *                        "adj4 adj5 .... adjN-1"
	 *                        "adj4 adj5 ...."
	 *                        .....
	 */
	for (il = from; il <= to;)
	{ /* adj1 .. adjN */
		for (back = to; back >= il; back--)
		{ /* back from adjN to adj[il] */
			/*
			 * Create teststring with "adj[il] .. adj[back]"
			 */
			adstr = "";
			for (sum = il; sum <= back; sum++)
			{ /* test "adj[il] ..
			 * adj[back]" */
				if (sum > il)
					adstr += " ";
				adstr += warr[sum];
			}
			if ((member_array(adstr, ids) < 0)
			        && (member_array(adstr, adjid_list_d) < 0))
				continue;
			else
			{
				il = back + 1; /* Match "adj[il] adj[il+1] .. adj[back]" */
				back = to;
				break;
			}
		}
		if (back < to)
			return 0;
	}
	return 1;
}
